package com.googlecode.skald.client.ui;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.PopupPanel;
import com.googlecode.skald.client.Skald;
import com.googlecode.skald.client.affordances.Affordance;
import com.googlecode.skald.client.affordances.Affordances;
import com.googlecode.skald.client.affordances.MenuOption;
import com.googlecode.skald.client.cmds.AffordanceCommand;
import com.googlecode.skald.client.cmds.PopupClosingCommand;

/**
 * Represents an object in the game's simulated world.  Tracks which anchors
 * in the GUI currently correspond to this object and memoizes the object's
 * affordance popup menu. 
 * <p>
 * Each game object is identified by its unique name (a String).  When sent
 * as the href of an anchor tag, this name should be preceded with a '?' 
 * character and may use urlencoding to support a wide range of name 
 * characters. See {@link #getObjectName(String)} for more.
 * <p>
 * Once created, a GameObject's name should be equivalent to any mentions of
 * that game object as a dobj or iobj in the current affordances.
 * <p>
 * GameObjects cannot be directly created outside of this package.  
 * For normal use, see {@link GameObjectRegistry} instead.
 * 
 * @author Zach Tomaszewski
 */
public class GameObject {
  
  //private static Logger logger = Logger.getLogger(GameObject.class.getName());
  
  private String name;
  private EnumSet<MenuOption> opts;
  private AffordanceCommand cmd;
  private PopupPanel popup;
  private List<Anchor> anchors;
  private List<HandlerRegistration> handlers;

  /**
   * Constructs a new GameObject with the given name and style.
   */
  protected GameObject(String objName, EnumSet<MenuOption> opts, 
                       AffordanceCommand cmd) {
    this.name = objName;
    this.opts = opts;
    this.cmd = cmd;
    this.anchors = new ArrayList<Anchor>();
    this.handlers = new ArrayList<HandlerRegistration>();
  }

  /** 
   * Returns a new affordance menu for this object with the given details.
   * 
   * @see Affordances#getMenu(List, EnumSet, AffordanceCommand)
   */
  public MenuBar getMenu() {
    return getMenu(this.opts, this.cmd);
  }
  
  private MenuBar getMenu(EnumSet<MenuOption> o, AffordanceCommand b) {
    List<Affordance> affords = Affordances.forObject(
        Skald.getGui().getAffordances(), this.name);
    return Affordances.getMenu(affords, o, b);
  }
  
  /** 
   * Returns a pop-up menu for this object.  This does not share the
   * regular menu version since menu items have the additional effect
   * of closing the popup when selected.
   */
  public PopupPanel getPopupMenu() {
    if (this.popup == null) {
      this.popup = new PopupPanel();
      MenuBar menu = this.getMenu(this.opts, 
                               new PopupClosingCommand(this.cmd, this.popup));
      menu.setAutoOpen(true);
      popup.setWidget(menu);
      popup.setAutoHideEnabled(true);
    }
    return this.popup;
  }
  
  /** Returns this GameObject's unique object name. */
  public String getName() {
    return this.name;
  }
  
  /**
   * Associates the given Anchor with this game object.  
   * Will associate a pop-up-menu-producing handler.
   */
  protected void add(final Anchor anc) {
    HandlerRegistration handler = anc.addDomHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {        
        PopupPanel popup = getPopupMenu();
        popup.showRelativeTo(anc);
        popup.show();
      }      
    }, ClickEvent.getType());
    this.anchors.add(anc);
    this.handlers.add(handler);
  }

  /**
   * Disables all anchors associated with this game object and removes all
   * anchor-associated click handlers. 
   */
  public void disable() {
    for (HandlerRegistration hr : this.handlers) {
      hr.removeHandler();
    }
    this.anchors.clear();
    this.handlers.clear();
    if (this.popup != null) {
      this.popup.hide();
      this.popup = null;
    }
  }

  /**
   * Given an absolute href URI, retrieves any decoded query portion of the 
   * URI as the name of a game world object.  If there is no query portion,
   * returns null.
   * <p>
   * When a game server sends response text, it should wrap any references
   * to an interactive game object with  
   * <code>&lt;a href="?name"&gt;description&lt;a&gt;</code>.  
   * <p>
   * The <code>description</code> can be any text that describes the object.
   * <p>  
   * The <code>name</code> should a unique and canonical name (ID) for that 
   * object.  This name String must match any reference to the object in the 
   * current affordances list.  The name and description may identical, and
   * often will be.
   * <p>
   * Note that the name is technically an href/URI.  It must be preceded by an
   * unescaped '?' character to mark it as a "query" relative to the current
   * document.  Other reserved characters in the name (including spaces, etc)
   * should be encoded using %-based url-encoding.  (See a URI specification 
   * for more info.)  To be conservative in what they send, game 
   * implementations should encode spaces as '%20' and not '+', even though
   * the object name appears in a query string. 
   * <p>
   * This method will attempt to decode any URL encoding before returning the
   * object name.  (Implementation note: This is currently done using 
   * com.google.gwt.http.client.URL.decode) 
   * <p>
   * Returns the decoded object name or null if there is ?query portion. 
   * 
   * @see AnchorElement#getHref()
   */
  public static String getObjectName(String href) {
    int last = href.lastIndexOf('?');
    if (last >= 0) {
      return com.google.gwt.http.client.URL.decode(href.substring(last + 1));
    }else {
      return null;
    }
  }
}
